package net.trentgardner.cordova.galaxygear; import java.util.ArrayList; import java.util.List; import org.apache.cordova.CordovaArgs; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaWebView; import org.apache.cordova.PluginResult; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.util.SparseArray; import net.trentgardner.cordova.galaxygear.GearMessageApi; import net.trentgardner.cordova.galaxygear.GearMessageListener; public class GalaxyGearPlugin extends CordovaPlugin { private final String TAG = GalaxyGearPlugin.class.getSimpleName(); private final String ACTION_ONCONNECT = "onConnect"; private final String ACTION_ONDATARECEIVED = "onDataReceived"; private final String ACTION_ONERROR = "onError"; private final String ACTION_SENDDATA = "sendData"; private final String PACKAGE_PREFERENCE = "GearProviderPackage"; private final String PROVIDER_CLASS = "net.trentgardner.cordova.galaxygear.GearProviderService"; private GearMessageApi api = null; private Intent serviceIntent = null; private SparseArray<GearConnection> connections = new SparseArray<GearConnection>(); private ServiceConnection serviceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { api = GearMessageApi.Stub.asInterface(service); try { api.addListener(messageListener); Log.i(TAG, "Listener registered with service"); } catch (RemoteException e) { Log.e(TAG, "Failed to add listener", e); } } public void onServiceDisconnected(ComponentName className) { api = null; Log.i(TAG, "Service connection closed!"); } }; private GearMessageListener.Stub messageListener = new GearMessageListener.Stub() { @Override public void onConnect(int connectionId) throws RemoteException { Log.d(TAG, "messageListener.onConnect"); createNewGearConnection(connectionId); } @Override public void onDataReceived(int connectionId, String data) throws RemoteException { Log.d(TAG, "messageListener.onDataReceived"); GearConnection connection = connections.get(connectionId); if (connection == null) createNewGearConnection(connectionId); else connection.onDataReceived(data); } @Override public void onError(int connectionId, String data) throws RemoteException { Log.d(TAG, "messageListener.onError"); GearConnection connection = connections.get(connectionId); if (connection != null) { connection.onError(data); connections.delete(connectionId); } } }; private GearConnection createNewGearConnection(int connectionId) { GearConnection connection = new GearConnection(connectionId); connections.put(connectionId, connection); notifyCallbacksOfConnection(connectionId); return connection; } private class GearConnection { private int mHandle; private List<CallbackContext> dataCallbacks = new ArrayList<CallbackContext>(); private List<CallbackContext> errorCallbacks = new ArrayList<CallbackContext>(); public GearConnection(int handle) { mHandle = handle; } public void addDataListener(CallbackContext callback) { dataCallbacks.add(callback); } public void addErrorListener(CallbackContext callback) { errorCallbacks.add(callback); } public void onDataReceived(String data) { notifyCallbacks(dataCallbacks, createJSONObject(mHandle, data)); } public void onError(String data) { notifyCallbacks(errorCallbacks, createJSONObject(mHandle, data)); } private void notifyCallbacks(final List<CallbackContext> callbacks, final JSONObject data) { cordova.getActivity().runOnUiThread(new Runnable() { public void run() { for (CallbackContext context : callbacks) { keepCallback(context, data); } } }); } } private List<CallbackContext> connectCallbacks = new ArrayList<CallbackContext>(); @Override public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); Activity context = cordova.getActivity(); final String packageName = preferences.getString(PACKAGE_PREFERENCE, context.getPackageName()); Log.d(TAG, "initialize - Watch Package: " + packageName); serviceIntent = new Intent(); serviceIntent.setClassName(packageName, PROVIDER_CLASS); Log.d(TAG, "Attempting to start service"); context.startService(serviceIntent); Log.d(TAG, "Attempting to bind to service"); context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); } @Override public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException { if (ACTION_ONCONNECT.equals(action)) onConnect(args, callbackContext); else if (ACTION_ONDATARECEIVED.equals(action)) onDataReceived(args, callbackContext); else if (ACTION_ONERROR.equals(action)) onError(args, callbackContext); else if (ACTION_SENDDATA.equals(action)) sendData(args, callbackContext); else return false; return true; } @Override public void onDestroy() { Log.d(TAG, "onDestroy"); try { Activity context = cordova.getActivity(); if (api != null) api.removeListener(messageListener); context.unbindService(serviceConnection); context.stopService(serviceIntent); } catch (Throwable t) { // catch any issues, typical for destroy routines // even if we failed to destroy something, we need to continue // destroying Log.w(TAG, "Failed to unbind from the service", t); } super.onDestroy(); } private void sendData(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { Log.d(TAG, "sendData"); int connectionId = args.getInt(0); String data = args.getString(1); try { if (api != null) { api.sendData(connectionId, data); callbackContext.success(); } else { callbackContext.error("Service not present"); } } catch (RemoteException e) { callbackContext.error(e.getMessage()); } } private void onConnect(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { Log.d(TAG, "onConnect"); connectCallbacks.add(callbackContext); // Alert the client of any existing connections int key = 0; for (int i = 0; i < connections.size(); i++) { key = connections.keyAt(i); connect(callbackContext, key); } } private void onDataReceived(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { Log.d(TAG, "onDataReceived"); int connectionId = args.getInt(0); GearConnection connection = connections.get(connectionId); if (connection != null) { connection.addDataListener(callbackContext); } else { callbackContext.error("Invalid connection handle"); } } private void onError(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { Log.d(TAG, "onError"); int connectionId = args.getInt(0); GearConnection connection = connections.get(connectionId); if (connection != null) { connection.addErrorListener(callbackContext); } else { callbackContext.error("Invalid connection handle"); } } private void connect(CallbackContext callbackContext, int connectionId) { JSONObject o = createJSONObject(connectionId, null); keepCallback(callbackContext, o); } private void notifyCallbacksOfConnection(final int connectionId) { this.cordova.getActivity().runOnUiThread(new Runnable() { public void run() { for (CallbackContext context : connectCallbacks) { connect(context, connectionId); } } }); } private void keepCallback(final CallbackContext callbackContext, JSONObject message) { PluginResult r = new PluginResult(PluginResult.Status.OK, message); r.setKeepCallback(true); callbackContext.sendPluginResult(r); } private JSONObject createJSONObject(int handle, String data) { JSONObject o = new JSONObject(); try { o.put("handle", handle); if (data != null) o.put("data", data); } catch (JSONException e) { e.printStackTrace(); } return o; } }